import os,datetime,time
from tinydb import TinyDB, Query
from fastapi import FastAPI
from starlette.requests import Request
import os
from fastapi.templating import Jinja2Templates
from starlette.staticfiles import StaticFiles
from starlette.responses import RedirectResponse
from typing import List
from fastapi import FastAPI, File, UploadFile
from starlette.responses import HTMLResponse
import json
import uvicorn
import _thread
import tempfile
def mkdir():
    try:
        os.mkdir(myfile)
    except:
        wait_lock()
        mkdir()

myfile='lock'
def wait_lock():
    while os.path.exists(myfile):
        time.sleep(1)

#请先把bootstatp的内容放入static文件夹内
location='static'
app = FastAPI()
app.mount('/static', StaticFiles(directory=location), name='static') 
#templates = Jinja2Templates(directory="templates")


#默认主目录
@app.get("/")
def main():
    print('success -1,显示主页内容')
    return HTMLResponse(content=html0)
    return RedirectResponse(url='/pc_list')

#首页的html代码
html0='''
<h2 align="center" ><a href="/help">windows集中管理平台</a></h2>
<form action="software_dir" method="get">
<h4>主机列表：
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
命令行列表：</h4>
<textarea rows="25" cols="80"  name="fn">
127.0.0.1\nlocalhost
</textarea>
<textarea rows="25" cols="80"  name="q">
ipconfig /all
</textarea>
 <br> <input type="submit" value="开始运行" /></br>
</form>
<a href="/history">历史记录</a>
<a href="/help">使用帮助</a>
'''

#fn是命令行， 取出来后写入bat文件中
#q是主机列表， 通过_thread执行多线程
@app.get("/software_dir")
def software_dir(request:Request, q: str = None,fn: str = None):
    #print(fn,q,fn.split('\r\n'))
    pc_list=list(filter(lambda x:x!='',fn.split('\r\n')))
    #print('pc_list',pc_list)
    command_list=list(filter(lambda x:x!='',q.split('\r\n')))
    #print('command_list',command_list,' & '.join(command_list))
    command_lists=' & '.join(command_list)
    with open ('a.bat','w') as f:
        f.writelines('\r\n'.join(command_list))
    time0=datetime.datetime.now().strftime("%Y-%m-%d %X")
    list_commands=q.split('\r\n')
    i=1
    for item in pc_list:
        print(i,'success 0 获取主机名',item,'\n')
        #_thread.start_new_thread( run,(command_lists,time0,item) )
        _thread.start_new_thread( run,(q.replace('\r\n','<br>'),time0,item) )
        print(i,'success 0.5 依据主机名下发多线程任务',item,'\n')
        i=i+1

    time.sleep(1)
    return RedirectResponse(url='/results?q='+time0)

#历史记录， 把批次号以set的形式取唯一从数据库里查询取出来，做超链接
@app.get("/history")
def history():
    db = TinyDB('db.json')
    User = Query()
    r=sorted(list(set(map(lambda x:x['time'],db))),reverse=True)
    x=list(map(lambda x:'<a href="/results?q={}" >{}</a>'.format(x,x),r))
    return HTMLResponse(content='<br>'.join(x))
    #return x

@app.get("/help")
def help():
    help0='''
    <br>此代码由xxx开发，前端使用fastapi,后台数据库使用tinydb,后端的程序使用微软的小工具psexec
    <br>如果客户端无响应， 需要修改注册表让psexec正常运行， 还需要修改防火墙，使服务器访问客户端的文件共享正常
    <br>确保服务器访问客服端 \\\\客户端\\admin$ 可以正常访问，不能访问的话就是防火墙问题
    <br>其次调试时 服务器 运行 psexec \\\\客户端 cmd  可是否正常， 不正常的话， 需要改注册表，并重启客户端
    '''
    return HTMLResponse(content=help0)

#以表格形式把结果展示出来，包含批次信息， 主机名称，状态，结果等， 结果做超链接， 链接到具体的结果
@app.get("/results")
def software_dir(request:Request, q: str = None):
    db = TinyDB('db.json')
    User = Query()

    r=db.search(User.time==q)
    db.close()
    #db.close()
    #print(x, type(x))
    X=list(map(lambda x:[x['time'],x['host'],x['status'],'<a href="/details?q={}&host={}" target="_blank" title="hello">{}</a>'.format(q,x['host'],x['result']),x['command']],r))
    #return HTMLResponse(content=(str(x).replace('\\n','<br>')))
    return HTMLResponse(content=html_head+tab_html('结果展示',['时间','主机','状态','结果','运行命令'],X).replace('失败','<font color="#FF0000">失败</font> ').replace('成功','<font color="#0000FF">成功</font> '))

#具体的结果信息， 需要两个输入， 一个执行时间的批次号， 一个是主机名，在tinydb数据库中查询后， 反馈回结果
@app.get("/details")
def software_dir(request:Request, q: str = None,host: str = None):
    db = TinyDB('db.json')
    User = Query()

    r=db.search((User.time==q) & (User.host==host))
    db.close()
    x=list(map(lambda x:x['detail'],r))
    #db.close()
    print(x, type(x))
    for item in x:
        print(type(item),item)
    #X=list(map(lambda x:[x['time'],x['host'],x['status'],'<a href="/details?q={}&host={}">{}</a>'.format(q,x['host'],x['result']),x['command']],r))
    #return HTMLResponse(content=(str(x).replace('\\n','<br>')))
    #return HTMLResponse(content=html_head+tab_html('结果展示',['时间','主机','状态','结果','运行命令'],X))
    return HTMLResponse(content='<br>'.join(x[0]))

#执行的函数，刚开始的时候使用db.insert把开始信息写入tinydb数据库
#等os.system('需要跑的内容')执行完成后， 把状态信息， 结果以及具体的log内容以db.update的方式更新入数据库中
#需要三个输入， 要执行的命令， 下单命令的时间作为统一的批次号码， 以及需要执行的远程主机的名称
def run(command,time,host):
    wait_lock()
    db = TinyDB('db.json')
    User = Query()
    #print('success 1',command,time,host,'\n')
    
    mkdir()
    db.insert({"time":time,"host":host,"command":command,"status":"开始","result":"请等待"})
    #db.insert({"time":time,"host":host,"status":"开始","result":"请等待"})
    #print('success 2',command,time,host,'\n')
    db.close()
    os.rmdir(myfile)
    temp_name = next(tempfile._get_candidate_names())
    #command0=command+' >>'+temp_name
    command0='psexec \\\\{} -c -f a.bat >> {}'.format(host,temp_name)
    #print('sucess 3 command0',command0)
    #x=os.system(command0+"& pause")
    x=os.system(command0)
    if x==0:
        x='成功'
    else:
        x='失败'
    #print('success 4 ', x)
    
    with open(temp_name,'r',encoding='GBK') as f:
        a=f.readlines()
    #print(a,type(a),''.join(a))
    wait_lock()
    mkdir()
    db = TinyDB('db.json')
    db.update({"status":"完成","result":x,"detail":a},(User.time==time)&(User.host==host))
    #{'count': 10}, Fruit.type == 'apple'
    db.close()
    os.rmdir(myfile)
    os.remove(temp_name)
    #print(a)

#table表格网页的写法，pyecharts也有现成的表格， 但是表格内不不能包含链接，所以我们借用了bootstrap的表格写法与pyecharts的写法，混写了一个函数
def tab_html(title,headers0,rows):
    start='''
    <table class="table table-condensed">
        <thead>
            <tr>
                <th>#</th>
                <th>Firstname</th>
                <th>2nname</th>
            </tr>
        </thead>
        '''
    headers='<body><h2>'+title+'<a href="/">返回主页</a>'+'</h2><table class="table table-bordered table-striped"><thead><tr>'
    for item in headers0:
        headers=headers+'<th>'+item+'</th>'
    headers=headers+'</tr></thead>'
    body='<tbody>'
    for item in rows:
        body=body+'<tr>'
        for x in item:
            body=body+'<td>'+str(x)+'</td>'
        body=body+'</tr>'
    body=body+'</tbody></table></body>'
    return headers+body
#<meta http-equiv="refresh" content="5">
html_head='''

<html>

  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="refresh" content="5">
    <!-- 上述3个meta标签*必须*放在最前面，任何其他内容都*必须*跟随其后！ -->
    <title>Bootstrap 101 Template</title>

    <!-- Bootstrap -->
    <link rel="stylesheet" href="/static/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">

    <!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询（media queries）功能 -->
    <!-- 警告：通过 file:// 协议（就是直接将 html 页面拖拽到浏览器中）访问页面时 Respond.js 不起作用 -->
    <!--[if lt IE 9]>
      <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
    <![endif]-->
  </head>

<body>
'''

if __name__ == "__main__":
    myfile='lock'
    #os.system('start iexplore http://127.0.0.1')
    os.system('start msedge http://127.0.0.1')
    #指定fastapi的运行端口
    uvicorn.run(app, host="0.0.0.0",port=80,log_level="info")
